Skip to content

PROTOTYPE: procedural weather moods on the day-cycle sky#84

Open
ryanbr wants to merge 7 commits into
mainfrom
prototype-weather-backgrounds
Open

PROTOTYPE: procedural weather moods on the day-cycle sky#84
ryanbr wants to merge 7 commits into
mainfrom
prototype-weather-backgrounds

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Prototype / not for merge as-is — exploring weather-style backgrounds that layer with the time-of-day gradient.

Approach

LiquidSky is already a layered Canvas (gradient → warm sheet → stars → settle). This slots a weather layer between the warm sheet and the stars, tinted by the current sky — so a cloud at dusk reads mauve, at noon white. Aesthetic only: no real conditions, no location/network (keeps NOOP offline).

Six moods, all procedural (no assets):

  • Clear (default — unchanged look)
  • Hazy — soft horizon-tinted band
  • Overcast — soft drifting radial clouds
  • Rain — clouds + falling streaks
  • Fog — rising veil
  • Snow — drifting specks

Opt-in via an experimental "Weather" picker in Appearance settings (menu style so it can't overflow like #43), gated on the day-cycle background being on.

How to try it

Sideload the testing build → Settings → Appearance → Weather (experimental) → cycle the modes and watch the Today background at different times of day.

Honest caveats

  • iOS/macOS only, and only the animated Today sky (static chart-tab skies stay plain for now).
  • Values (cloud count/opacity, rain density, drift speed) are a first pass to tune on-device — I can't preview SwiftUI Canvas on WSL, so the testing build is the compile + visual check.
  • If procedural clouds don't look rich enough, the fallback is blended cloud art (the option we set aside) — but this proves the concept in the real architecture first.

Files: Strand/Liquid/LiquidSky.swift, Strand/Screens/SettingsView.swift.

ryanbr added 7 commits July 8, 2026 20:11
Explores weather-style backgrounds that work WITH the existing time-of-day
gradient (the idea in discussion). Aesthetic only — no real conditions, no
location/network, so NOOP stays offline.

- LiquidSky: a new weather layer between the warm sheet and the starfield,
  tinted by the CURRENT sky so a cloud at dusk reads mauve and at noon white.
  Six moods, all procedural Canvas fills (no assets): clear (default), hazy,
  overcast (soft drifting radial clouds), rain (clouds + falling streaks), fog
  (rising veil), snow (drifting specks). Animated on the Today sky.
- Appearance settings: an experimental "Weather" menu picker (menu style so it
  can't overflow like #43), gated on the day-cycle background being on. Default
  clear = today's look unchanged.

PROTOTYPE — iOS/macOS, Today background only; values are a first pass to tune
on-device (SwiftUI Canvas can't be previewed off-device). Static-tab skies are
left plain for now.
Android's Today background is the same gradient sky as iOS (LiquidSky.kt, a 1:1
port), so this mirrors the iOS prototype exactly rather than touching the image
scenes.

- LiquidSky.kt: a weather layer in the shared renderLiquidSky (so it covers BOTH
  the animated Today sky AND the static scaffold sky in one place — the gap the
  iOS side has), tinted by the current sky. Six moods matching iOS: clear
  (default), hazy, overcast, rain, fog, snow. LiquidWeather enum + a reactive
  LiquidWeatherState (mirrors CardAppearance) so the picker live-updates.
- NoopPrefs: weather getter/setter, stored under the SAME "liquid.weather" raw
  string as iOS. Loaded at launch (LiquidWeatherState.init in MainActivity).
- Settings → Appearance: a "Weather (experimental)" SegmentedPillControl below
  the day-cycle toggle, gated on it, live-updating the sky.

PROTOTYPE — values are a first pass to tune on-device. Android compiles locally.
…ocedural fallback)

The procedural moods read a bit muted; add an IMAGE path so richer art can drive
each mood, with the procedural draw as the fallback when no art is present — so
the app still works with zero assets and dropping a file in upgrades that mood
with no code change.

- iOS/macOS (LiquidSky.swift): loadWeatherImage() resolves weather_<mood> from the
  asset catalog (UIImage/NSImage); drawWeatherLayer draws it blended (.screen, 0.85
  — tunable) over the gradient, else falls back to the procedural draw.
- Android (LiquidSky.kt): rememberWeatherImage() resolves weather_<mood> from
  res/drawable via getIdentifier (0 = absent → fallback); rendered with
  drawImage(blend=Screen, alpha=0.85) in the shared render core.
- docs/WEATHER_ASSETS.md: exact asset names, where the files go per platform, art
  guidance, and a REQUIRED licence table — every weather image must have its source
  + licence recorded before committing (unlike the inherited scene1-10 backdrops).

No art committed yet — supply weather_<mood> files (self-generated / licensed) and
they render automatically. Blend + opacity are on-device tuning knobs. Android
compiles locally.
First real weather art: weather_rain (1500x1000) into the iOS asset catalogs
(Strand + StrandiOS) and Android res/drawable-nodpi. The Rain mood now draws the
image instead of the procedural streaks.

- Blend default changed .screen -> normal source-over (both platforms): the
  supplied art is an OPAQUE storm scene, which source-over lets cover/darken the
  sky (screen would drop the dark background and only lighten). Source-over is
  also the universal default — a transparent overlay still shows the gradient
  through its alpha. Opacity 0.9 (tunable).
- docs/WEATHER_ASSETS.md: license row for weather_rain flagged TO CONFIRM.

NB: weather_rain provenance/license is NOT yet recorded — must be confirmed before
this ships or merges (prototype/testing only for now). Android compiles locally.
…retch"/"doubled")

Android drew the weather image aspect-fit-to-width, top-aligned — a 3:2 landscape
storm image covered only the top ~2/3 of the tall sky band with a hard bottom edge,
which read as "not stretched" and (split by a floating card over its middle) as
"doubled". Now stretches to fill the whole sky canvas (dstSize = w×h), matching the
iOS draw; the settle fade dissolves the lower part into the page. Android compiles.
…ix scroll stutter)

A weather image is static, but the animated sky (Today) re-blitted the full-screen
bitmap every frame (20fps) — behind a scrolling list that stutters, and it hides
the animated breath/stars underneath anyway. Now the sky poses ONCE (no frame loop)
when an image is present, so scroll composites a cached texture instead of a
per-frame redraw. Procedural moods + the plain sky keep the live animation.

- iOS: LiquidSky.body skips TimelineView when loadWeatherImage() resolves.
- Android: LiquidSky takes the static (no withFrameNanos) path when weatherImage != null.

Android compiles locally.
…r the image)

The weather image is a soft, stretched background wash, so full 1500px res buys no
visible quality but quadruples the texture the translucent scrolling cards must
composite over each frame — the residual card stutter on the (already-static)
Settings sky. Decode at half res (inSampleSize=2, ~750px). Android compiles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant